package service

import (
	"context"
	"database/sql"
	"fx_swift_agent/api"
	"fx_swift_agent/internal/model"
	"fx_swift_agent/pkg/code"
	"fx_swift_agent/pkg/crypt"
	"fx_swift_agent/pkg/global"
	"fx_swift_agent/pkg/internalerrors"
	"fx_swift_agent/pkg/jwt"
	"fx_swift_agent/pkg/logger"
	"fx_swift_agent/pkg/mail"
	"fx_swift_agent/pkg/utils"
	"github.com/gogf/gf/v2/container/gvar"
	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/spf13/cast"
)

type ILoginSrv interface {
	Register(ctx context.Context, req *api.RegisterReq) (*api.LoginRes, error)

	Login(ctx context.Context, req *api.LoginReq) (*api.LoginRes, error)

	SendMailCode(ctx context.Context, mail string) error

	ForgetPassword(ctx context.Context, req *api.ForgetPasswordReq) error

	UpdatePassword(ctx context.Context, req *api.UpdatePasswordReq) error

	SignOut(ctx context.Context) error

	GetAgentInfo(ctx context.Context) (*api.GetAgentInfoRes, error)

	EmailIsExit(ctx context.Context, email string) (bool, error)
}

type loginSrv struct {
}

func NewLoginSrv() ILoginSrv {
	return &loginSrv{}
}

// 注册
func (l *loginSrv) Register(ctx context.Context, req *api.RegisterReq) (*api.LoginRes, error) {
	var (
		err            error
		hashedPassword string
		salt           string
		result         sql.Result
		num            int
		rowsNums       int64
		redisResult    *gvar.Var
		redisCodes     string
		account        string
		token          string
	)
	// 比对验证码
	redisResult, err = g.Redis().Get(ctx, req.Email)
	if err != nil {
		logger.SugarLogger.Error(err)
		return nil, internalerrors.New(code.ErrUnknown, "验证码不正确")
	}
	if redisResult.Val() != nil {
		redisCodes = redisResult.String()
		if redisCodes != req.Code {
			return nil, internalerrors.New(code.ErrUnknown, "验证码不正确")
		}
	} else {
		return nil, internalerrors.New(code.ErrUnknown, "验证码不正确")
	}

	// 查询邮箱是否被注册
	if num, err = global.GFDB.Ctx(ctx).Model("fx_agent").Where("email", req.Email).Count(); err != nil || num == 1 {
		return nil, internalerrors.New(code.ErrUnknown, "邮箱已经被注册了")
	}

	if hashedPassword, salt, err = crypt.HashPassword(req.Password); err != nil {
		return nil, internalerrors.New(code.ErrUnknown, err.Error())
	}

	account = utils.GetHashCode(utils.GenerateCode32())
	err = global.GFDB.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		if result, err = tx.Insert("fx_agent", &model.FxAgent{
			Account:   account,
			Email:     req.Email,
			Password:  hashedPassword,
			NickName:  req.Name,
			Salt:      salt,
			AppId:     utils.GenerateAppID(),
			AppSecret: utils.GenerateAppSecret(),
			IsFreeze:  1,
			Fee:       cast.ToFloat64(global.ViperObj.GetFloat64("card.recharge_fee")),
			CountryId: cast.ToString(req.CountryId),
		}); err != nil {
			logger.SugarLogger.Error(err)
			return internalerrors.New(code.ErrUnknown, "注册失败")
		}
		rowsNums, err = result.RowsAffected()
		if rowsNums == 0 {
			return internalerrors.New(code.ErrUnknown, "注册失败")
		}
		if result, err = tx.Insert("fx_enterprise_account_apply", &model.FxEnterpriseAccountApply{
			Account: account,
			Status:  "draft",
		}); err != nil {
			logger.SugarLogger.Error(err)
			return internalerrors.New(code.ErrUnknown, "注册失败")
		}
		rowsNums, err = result.RowsAffected()
		if rowsNums == 0 {
			return internalerrors.New(code.ErrUnknown, "注册失败")
		}

		if result, err = tx.Insert("fx_account", &model.FxAccount{
			Account:     account,
			AccountType: "agent",
		}); err != nil {
			logger.SugarLogger.Error(err)
			return internalerrors.New(code.ErrUnknown, "注册失败")
		}
		rowsNums, err = result.RowsAffected()
		if rowsNums == 0 {
			return internalerrors.New(code.ErrUnknown, "注册失败")
		}
		return nil
	})
	if err == nil {
		// generate token
		if token, err = jwt.Gen(account, req.Email, "agent"); err != nil {
			return nil, internalerrors.New(code.ErrUnknown, "token generate failed")
		}

		// set token to redis
		if err = g.Redis().SetEX(ctx, account, token, global.ViperObj.GetInt64("token.expired_time")); err != nil {
			return nil, internalerrors.New(code.ErrUnknown, "token keep failed")
		}
	}
	return &api.LoginRes{
		Token:   token,
		Email:   req.Email,
		Account: account,
		Status:  "draft",
		Name:    req.Name,
	}, nil
}

// 登陆
func (l *loginSrv) Login(ctx context.Context, req *api.LoginReq) (*api.LoginRes, error) {
	var (
		err             error
		token           string
		agentObj        *model.FxAgent
		accountApplyObj *model.FxEnterpriseAccountApply
	)
	// 根据帐号查询
	if err = global.GFDB.Ctx(ctx).Model("fx_agent").Where("email=?", req.Email).
		Scan(&agentObj); err != nil {
		logger.SugarLogger.Error(err)
		return nil, internalerrors.New(code.ErrUnknown, "该帐号不存在")
	}

	if agentObj == nil {
		return nil, internalerrors.New(code.ErrUnknown, "该帐号不存在")
	}

	if agentObj.IsFreeze == 0 { // 帐户被冻结
		return nil, internalerrors.New(code.ErrUnknown, "该帐号已被冻结")
	}

	if err = global.GFDB.Ctx(ctx).Model("fx_enterprise_account_apply").Where("account=?", agentObj.Account).Scan(&accountApplyObj); err != nil {
		logger.SugarLogger.Error(err)
		return nil, internalerrors.New(code.ErrUnknown, "该帐号数据存在异常")
	}

	// compare password
	if err = agentObj.ComparePassword(req.Password); err != nil {
		return nil, internalerrors.New(code.ErrPasswordIncorrect, "密码不匹配")
	}

	// generate token
	if token, err = jwt.Gen(agentObj.Account, agentObj.Email, "agent"); err != nil {
		return nil, internalerrors.New(code.ErrUnknown, "token generate failed")
	}

	// set token to redis
	if err = g.Redis().SetEX(ctx, agentObj.Account, token, global.ViperObj.GetInt64("token.expired_time")); err != nil {
		return nil, internalerrors.New(code.ErrUnknown, "token keep failed")
	}

	return &api.LoginRes{
		Token:   token,
		Email:   agentObj.Email,
		Account: agentObj.Account,
		Status:  accountApplyObj.Status,
		Name:    agentObj.NickName,
	}, nil
}

// 退出登陆
func (l *loginSrv) SignOut(ctx context.Context) error {
	var (
		err          error
		resultNums   int64
		accountToken string
	)
	accountToken = ctx.Value("Account").(string)
	resultNums, err = g.Redis().Del(ctx, accountToken)
	if err != nil || resultNums == 0 {
		return internalerrors.New(code.ErrBind, "帐号异常")
	}
	return err
}

// 发送code到邮箱
func (l *loginSrv) SendMailCode(ctx context.Context, email string) error {
	var (
		err error
	)
	defer func() {
		if r := recover(); r != nil {
			logger.SugarLogger.Error(err)
		}
	}()
	// generate random validation code
	codes := utils.GenerateVerificationCode(6)

	if !VerifyEmailFormat(email) {
		return internalerrors.New(code.ErrUnknown, "邮箱格式不正确")
	}

	// 存入redis中并且设置过期时间
	if err = g.Redis().SetEX(ctx, email, codes, 300); err != nil {
		return internalerrors.New(code.ErrUnknown, "验证码发送失败")
	}
	go mail.SendMail(email, codes)

	return err
}

// 忘记密码
func (l *loginSrv) ForgetPassword(ctx context.Context, req *api.ForgetPasswordReq) error {
	var (
		agentObj             *model.FxAgent
		err                  error
		redisResult          *gvar.Var
		redisCodes           string
		result               sql.Result
		rowsNums             int64
		hashedPassword, salt string
	)

	// 根据帐号查询
	if err = global.GFDB.Ctx(ctx).Model("fx_agent").Where("email=?", req.Email).
		Scan(&agentObj); err != nil {
		return internalerrors.New(code.ErrBind, "帐号查询异常")
	}
	if agentObj == nil {
		return internalerrors.New(code.ErrUnknown, "请先去注册")
	}
	// 比对验证码
	redisResult, err = g.Redis().Get(ctx, agentObj.Email)
	if err != nil {
		return internalerrors.New(code.ErrUnknown, "验证码不正确")
	}
	if redisResult.Val() != nil {
		redisCodes = redisResult.String()
		if redisCodes != req.Code {
			return internalerrors.New(code.ErrUnknown, "验证码不正确")
		}
	} else {
		return internalerrors.New(code.ErrUnknown, "验证码不正确")
	}
	// update password
	hashedPassword, salt, err = crypt.HashPassword(req.NewPassword)
	if err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}
	result, err = global.GFDB.Model("fx_agent").Data(g.Map{"password": hashedPassword, "salt": salt}).
		Where("account=?", agentObj.Account).Update()
	if err != nil {
		return internalerrors.New(code.ErrUnknown, "更新密码失败")
	}
	rowsNums, err = result.RowsAffected()
	if rowsNums == 0 {
		return internalerrors.New(code.ErrUnknown, "更新密码失败")
	}
	return nil
}

// 更新密码
func (l *loginSrv) UpdatePassword(ctx context.Context, req *api.UpdatePasswordReq) error {
	var (
		agentObj *model.FxAgent
		err      error
		account  string
	)
	account = ctx.Value("Account").(string)
	// 根据帐号查询
	if err = global.GFDB.Ctx(ctx).Model("fx_agent").Where("account=?", account).
		Scan(&agentObj); err != nil || agentObj == nil {
		return internalerrors.New(code.ErrUnknown, "更新密码失败")
	}
	if err = agentObj.ComparePassword(req.OldPassword); err != nil {
		return internalerrors.New(code.ErrPasswordIncorrect, "password not match")
	}
	hashedPassword, salt, err := crypt.HashPassword(req.NewPassword)
	if err != nil {
		return internalerrors.New(code.ErrUnknown, "更新密码失败")
	}
	_, err = global.GFDB.Model("fx_agent").Data(g.Map{"password": hashedPassword, "salt": salt}).
		Where("account=?", account).Update()
	if err != nil {
		return internalerrors.New(code.ErrUnknown, "更新密码失败")
	}
	return err
}

// 获取登陆用户信息
func (l *loginSrv) GetAgentInfo(ctx context.Context) (*api.GetAgentInfoRes, error) {
	var (
		err             error
		account         string
		agentObj        *model.FxAgent
		accountApplyObj *model.FxEnterpriseAccountApply
		managementObj   *model.FxAgentManagement
		effectiveDate   string
		managementFee   string
	)
	account = ctx.Value("Account").(string)
	// 根据帐号查询
	if err = global.GFDB.Ctx(ctx).Model("fx_agent").Where("account=?", account).
		Scan(&agentObj); err != nil || agentObj == nil {
		logger.SugarLogger.Error(err)
		panic(internalerrors.New(code.ErrBind, "该帐号存在异常"))
	}

	if err = global.GFDB.Ctx(ctx).Model("fx_enterprise_account_apply").Where("account=?", agentObj.Account).
		Scan(&accountApplyObj); err != nil {
		logger.SugarLogger.Error(err)
		return nil, internalerrors.New(code.ErrUnknown, "该帐号数据存在异常")
	}

	if err = global.GFDB.Ctx(ctx).Model("fx_agent_management").Where("account=?", agentObj.Account).
		Scan(&managementObj); err != nil {
		logger.SugarLogger.Error(err)
		return nil, internalerrors.New(code.ErrUnknown, "该帐号数据存在异常")
	}
	if managementObj != nil {
		effectiveDate = managementObj.EffectiveDate
		managementFee = managementObj.ManagementFee.String()
	}

	return &api.GetAgentInfoRes{
		Email:          agentObj.Email,
		Account:        agentObj.Account,
		Status:         accountApplyObj.Status,
		Name:           accountApplyObj.CompanyName,
		NickName:       agentObj.NickName,
		AppId:          agentObj.AppId,
		AppSecret:      agentObj.AppSecret,
		EffectiveDate:  effectiveDate,
		ManagementFee:  managementFee,
		Fee:            cast.ToString(agentObj.Fee),
		CardOpeningFee: cast.ToString(agentObj.CardOpeningFee),
	}, nil
}

// 判断邮箱是否存在
func (l *loginSrv) EmailIsExit(ctx context.Context, email string) (bool, error) {
	var (
		err error
		num int
	)
	num, err = global.GFDB.Ctx(ctx).Model("fx_agent").Where("email=?", email).Count()
	if err != nil {
		return false, internalerrors.New(code.ErrUnknown, "查询数据有误")
	}
	if num == 1 {
		return true, err
	}
	return false, err
}
